/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    binary_serializer.cpp  
  Description: 

*/

#include "stdafx.h"

using namespace m::alloc;
using namespace m::error;

// Amount by which to resize the record array when needed.
#define RESIZE_FACTOR 16

namespace m
{
	namespace io
	{
		binary_serializer::binary_serializer()
			: _Records(nullptr),
			_CountRecords(0),
			_LastRecord(0),
			_CurRecord(nullptr)
		{
		}

		binary_serializer::~binary_serializer()
		{
			ClearRecords();
		}

		void binary_serializer::write_object_start(const wchar_t* name)
		{
			assert(name);
			if (name)
			{
				if (_CurRecord && ValueFor(name, TypeObject) != nullptr)
				{
					throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_DUP_NAME), "Cannot write object, value exists.");
				}

				_CurRecord = InsertValue(name, nullptr, 0, TypeObject);
			}
		}

		void binary_serializer::write_object_end()
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot write object end, not currently writing an object.");
			}

			for (size_t i = 0; i < _LastRecord; ++i)
			{
				if (_Records[i]->Id == _CurRecord->Parent)
				{
					_CurRecord = _Records[i];
					return;
				}
			}

			_CurRecord = nullptr;
		}

		void binary_serializer::write_value(const wchar_t* name, const wchar_t* string)
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot write string, not currently writing an object.");
			}

			if (string)
			{
				size_t sz = sizeof(wchar_t) * (wcslen(string) + 1);
				InsertValue(name, (const byte*)string, sz, TypeString);
			}
			else
			{
				InsertValue(name, nullptr, 0, TypeUnknown);
			}
		}

		void binary_serializer::write_value(const wchar_t* name, double num)
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot write number, not currently writing an object.");
			}

			InsertValue(name, (const byte*)&num, sizeof(num), TypeDouble);
		}

		void binary_serializer::write_value(const wchar_t * name, int8_t num)
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot write number, not currently writing an object.");
			}

			InsertValue(name, (const byte*)&num, sizeof(num), TypeInt8);
		}

		void binary_serializer::write_value(const wchar_t * name, int16_t num)
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot write number, not currently writing an object.");
			}

			InsertValue(name, (const byte*)&num, sizeof(num), TypeInt16);
		}

		void binary_serializer::write_value(const wchar_t* name, int32_t num)
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot write number, not currently writing an object.");
			}

			InsertValue(name, (const byte*)&num, sizeof(num), TypeInt32);
		}

		void binary_serializer::write_value(const wchar_t* name, int64_t num)
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot write long, not currently writing an object.");
			}

			InsertValue(name, (const byte*)&num, sizeof(num), TypeInt64);
		}

		void binary_serializer::write_value(const wchar_t* name, bool bit)
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot write bool, noy currently writing an object.");
			}

			InsertValue(name, (const byte*)&bit, sizeof(bit), TypeBool);
		}

		void binary_serializer::write_value(const wchar_t * name, const byte * data, size_t sz)
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot write binary, not writing an object.");
			}
			if (sz > SIZE_MAX)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_BAD_LENGTH), "Size too large.");
			}

			InsertValue(name, (const byte*)data, sz, TypeUnknown);
		}

		bool binary_serializer::read_object_start(const wchar_t* name, size_t* countObjects)
		{
			if (countObjects)
			{
				*countObjects = 0;
			}
			BinaryRecord* cur = ValueFor(name, TypeObject);
			if (cur)
			{
				if (countObjects)
				{
					*countObjects = (size_t)cur->CountData;
				}
				_CurRecord = cur;
				return true;
			}
			return false;
		}

		void binary_serializer::read_object_end()
		{
			if (!_CurRecord)
			{
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_INVALID_OPERATION), "Cannot read object end, value not an object.");
			}

			for (size_t i = 0; i < _LastRecord; ++i)
			{
				if (_Records[i]->Id == _CurRecord->Parent)
				{
					_CurRecord = _Records[i];
					return;
				}
			}

			_CurRecord = nullptr;
		}

		void binary_serializer::read_value(const wchar_t* name, wchar_t** string)
		{
			assert(name && string);
			if (name && string)
			{
				clear(*string);

				BinaryRecord* cur = ValueFor(name, TypeString);
				if (cur)
				{
					assert(cur->CountData <= SIZE_MAX);
					if (cur->CountData <= SIZE_MAX)
					{
						*string = (wchar_t*)malloc((size_t)cur->CountData + sizeof(wchar_t));
						if (!*string)
						{
							throw std::bad_alloc();
						}

						memset(*string, 0, (size_t)cur->CountData + sizeof(wchar_t));
						memcpy_s(*string, (size_t)cur->CountData, cur->StartOfData, (size_t)cur->CountData);
					}
				}
			}
		}

		void binary_serializer::read_value(const wchar_t* name, double* num)
		{
			assert(name && num);
			if (name && num)
			{
				*num = 0.;
				BinaryRecord* cur = ValueFor(name, TypeDouble);
				if (cur)
				{
					*num = *((double*)cur->StartOfData);
				}
			}
		}

		void binary_serializer::read_value(const wchar_t * name, int8_t * num)
		{
			assert(name && num);
			if (name && num)
			{
				*num = 0;
				BinaryRecord* cur = ValueFor(name, TypeInt8);
				if (cur)
				{
					*num = *((int8_t*)cur->StartOfData);
				}
			}
		}

		void binary_serializer::read_value(const wchar_t * name, int16_t * num)
		{
			assert(name && num);
			if (name && num)
			{
				*num = 0;
				BinaryRecord* cur = ValueFor(name, TypeInt16);
				if (cur)
				{
					*num = *((int16_t*)cur->StartOfData);
				}
			}
		}

		void binary_serializer::read_value(const wchar_t* name, int32_t* num)
		{
			assert(name && num);
			if (name && num)
			{
				*num = 0;
				BinaryRecord* cur = ValueFor(name, TypeInt32);
				if (cur)
				{
					*num = *((int32_t*)cur->StartOfData);
				}
			}
		}

		void binary_serializer::read_value(const wchar_t* name, int64_t* num)
		{
			assert(name && num);
			if (name && num)
			{
				*num = 0;
				BinaryRecord* cur = ValueFor(name, TypeInt64);
				if (cur)
				{
					*num = *((int64_t*)cur->StartOfData);
				}
			}
		}

		void binary_serializer::read_value(const wchar_t* name, bool* bit)
		{
			assert(name && bit);
			if (name && bit)
			{
				*bit = false;
				BinaryRecord* cur = ValueFor(name, TypeBool);
				if (cur)
				{
					*bit = *((bool*)cur->StartOfData);
				}
			}
		}

		void binary_serializer::read_value(const wchar_t * name, byte ** data, size_t * sz)
		{
			assert(name && data && sz);
			if (name && data && sz)
			{
				if (*data)
				{
					free(*data);
					*data = nullptr;
				}
				*sz = 0;
				BinaryRecord* cur = ValueFor(name, TypeUnknown);
				if (cur)
				{
					if (cur->CountData > SIZE_MAX)
					{
						throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_BAD_LENGTH), "Size too large.");
					}
					size_t dstSize = (size_t)cur->CountData;
					*data = (byte*)malloc(dstSize);
					if (!*data)
					{
						throw std::bad_alloc();
					}
					memcpy_s(*data, dstSize, cur->StartOfData, dstSize);
					*sz = dstSize;
				}
			}
		}

		void binary_serializer::ClearRecords()
		{
			// Some init code here.
			if (_Records)
			{
				for (size_t i = 0; i < _CountRecords; ++i)
				{
					if (_Records[i])
					{
						delete _Records[i];
					}
				}
				delete[] _Records;
				_Records = nullptr;
			}
		}

		void binary_serializer::load(file_buffer * buffer)
		{
			assert(buffer);
			if (buffer)
			{
				ClearRecords();
				_CountRecords = 0;
				_LastRecord = 0;
				_CurRecord = nullptr;

				buffer->set_offset(0);

				uint64_t Size;
				size_t size = buffer->read((char*)&Size, sizeof(Size));

				if (size == sizeof(Size) && Size == buffer->size())
				{
					uint16_t Checksum;
					size_t read = buffer->read((char*)&Checksum, sizeof(Checksum));

					if (read == sizeof(Checksum))
					{
						// Next load each record into the record table, validating it along the way.
						read = buffer->read((char*)&_CountRecords, sizeof(int64_t));
						if (read == sizeof(int64_t))
						{
							// First validate the checksum matches.
							uint16_t ActualChecksum = CalculateChecksum(buffer);
							if (Checksum != ActualChecksum)
							{
								throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_FILE_CORRUPT), "Failed to load binary serialized data, checksum mismatch.");
							}
							buffer->set_offset(sizeof(Checksum) + sizeof(Size) + sizeof(int64_t));

							_Records = new BinaryRecord*[_CountRecords];
							for (_LastRecord = 0; _LastRecord < _CountRecords; ++_LastRecord)
							{
								_Records[_LastRecord] = new BinaryRecord();
								buffer->read((char*)&_Records[_LastRecord]->Id, sizeof(_Records[_LastRecord]->Id));
								uint8_t tType;
								buffer->read((char*)&tType, sizeof(tType));
								_Records[_LastRecord]->Type = (binary_serializer::BinaryType)tType;
								buffer->read((char*)&_Records[_LastRecord]->CchName, sizeof(_Records[_LastRecord]->CchName));
								if (_Records[_LastRecord]->CchName > 0)
								{
									assert(_Records[_LastRecord]->CchName < SIZE_MAX);
									if (_Records[_LastRecord]->CchName < SIZE_MAX)
									{
										_Records[_LastRecord]->Name = (wchar_t*)malloc(((size_t)_Records[_LastRecord]->CchName + 1) * sizeof(wchar_t));
										if (!_Records[_LastRecord]->Name)
										{
											throw std::bad_alloc();
										}
										buffer->read((char*)_Records[_LastRecord]->Name, (size_t)_Records[_LastRecord]->CchName * sizeof(wchar_t));
										_Records[_LastRecord]->Name[(size_t)_Records[_LastRecord]->CchName] = 0;
									}
								}
								buffer->read((char*)&_Records[_LastRecord]->CountData, sizeof(_Records[_LastRecord]->CountData));
								if (_Records[_LastRecord]->Type != TypeObject && _Records[_LastRecord]->CountData > 0)
								{
									assert(_Records[_LastRecord]->CountData <= SIZE_MAX);
									if (_Records[_LastRecord]->CountData <= SIZE_MAX)
									{
										_Records[_LastRecord]->StartOfData = new byte[(size_t)_Records[_LastRecord]->CountData];
										buffer->read((char*)_Records[_LastRecord]->StartOfData, (size_t)_Records[_LastRecord]->CountData);
									}
								}
								buffer->read((char*)&_Records[_LastRecord]->Parent, sizeof(_Records[_LastRecord]->Parent));
							}
						}

						// We successfully loaded the document and read each record. Hooray. The root object should be whatever we loaded as the first one.
						return;
					}
				}
				throw bad_result_exception(MONORET(FACILITY_BINARY_SERIALIZER, ERROR_FILE_CORRUPT), "Failed to load binary serialized data, invalid data.");
			}
		}

		void binary_serializer::save(file_buffer * buffer)
		{
			assert(buffer);
			if (buffer)
			{
				buffer->set_size(0);
				buffer->set_offset(0);

				// Write a stub checksum. We'll update the file with the real checksum later.
				uint64_t Size = 0;
				buffer->write((const char*)&Size, sizeof(Size));
				uint16_t Checksum = 0;
				buffer->write((const char*)&Checksum, sizeof(Checksum));
				// Next write the count of records
				int64_t count = _LastRecord;
				buffer->write((const char*)&count, sizeof(count));

				for (size_t i = 0; i < (size_t)count; ++i)
				{
					buffer->write((const char*)&_Records[i]->Id, sizeof(_Records[i]->Id));
					uint8_t tType = (uint8_t)_Records[i]->Type;
					buffer->write((const char*)&tType, sizeof(tType));
					buffer->write((const char*)&_Records[i]->CchName, sizeof(_Records[i]->CchName));
					assert(_Records[i]->CchName <= SIZE_MAX);
					if (_Records[i]->CchName <= SIZE_MAX)
					{
						if (_Records[i]->CchName > 0)
						{
							buffer->write((const char*)_Records[i]->Name, (size_t)_Records[i]->CchName * sizeof(wchar_t));
						}
					}
					buffer->write((const char*)&_Records[i]->CountData, sizeof(_Records[i]->CountData));
					if (_Records[i]->Type != TypeObject && _Records[i]->CountData > 0)
					{
						assert(_Records[i]->CountData <= SIZE_MAX);
						if (_Records[i]->CountData <= SIZE_MAX)
						{
							buffer->write((const char*)_Records[i]->StartOfData, (size_t)_Records[i]->CountData);
						}
					}
					buffer->write((const char*)&_Records[i]->Parent, sizeof(_Records[i]->Parent));
				}

				// Finally, compute and write the checksum.
				buffer->set_offset(sizeof(Size) + sizeof(Checksum) + sizeof(int64_t));
				Checksum = CalculateChecksum(buffer);
				buffer->set_offset(0);
				Size = buffer->size();
				buffer->write((char*)&Size, sizeof(Size));
				buffer->write((char*)&Checksum, sizeof(Checksum));
				buffer->write((char*)&_CountRecords, sizeof(int64_t));
				assert(Size < SIZE_MAX);
				if (Size < SIZE_MAX)
				{
					buffer->set_offset((size_t)Size - 1);
				}
			}
		}

		binary_serializer::BinaryRecord::BinaryRecord()
			:
			Type(TypeUnknown),
			CchName(0),
			Name(nullptr),
			CountData(0),
			StartOfData(nullptr),
			Parent(ID_NOTARCHIVED)
		{
		}

		binary_serializer::BinaryRecord::~BinaryRecord()
		{
			clear(Name);

			if (StartOfData)
			{
				delete[] StartOfData;
			}
		}

		void* binary_serializer::BinaryRecord::operator new(size_t sz)
		{
			return ::operator new(sz, pool);
		}

			void binary_serializer::BinaryRecord::operator delete(void* ptr)
		{
			::operator delete(ptr, pool);
		}

		binary_serializer::BinaryRecord* binary_serializer::InsertValue(const wchar_t* name, const byte* data, int64_t sz, BinaryType type)
		{
			if (_CountRecords == 0 || _Records[_CountRecords - 1] != nullptr)
			{
				// Time for a resize!
				if (_CountRecords > 0)
				{
					_CountRecords *= RESIZE_FACTOR;
				}
				else
				{
					_CountRecords = RESIZE_FACTOR;
				}
				BinaryRecord** NewRecords = new BinaryRecord*[_CountRecords];
				for (size_t i = 0; i < _LastRecord; ++i)
				{
					NewRecords[i] = _Records[i];
				}

				for (size_t i = _LastRecord; i < _CountRecords; ++i)
				{
					NewRecords[i] = nullptr;
				}

				// Delete the old array.
				if (_Records)
				{
					delete[] _Records;
				}

				// Substituting the new.
				_Records = NewRecords;
			}

			assert(_LastRecord < _CountRecords);
			if (_LastRecord < _CountRecords)
			{
				BinaryRecord* value = new BinaryRecord();
				_Records[_LastRecord] = value;
				++_LastRecord;

				value->Id = _LastRecord;
				value->Type = type;
				assign(value->Name, name);
				if (value->Name)
				{
					value->CchName = wcslen(value->Name);
				}
				value->CountData = sz;
				if (value->Type != TypeObject && value->CountData > 0)
				{
					value->StartOfData = new byte[(size_t)value->CountData];
					memcpy_s(value->StartOfData, (size_t)value->CountData, data, (size_t)value->CountData);
				}

				if (_CurRecord)
				{
					value->Parent = _CurRecord->Id;
					++_CurRecord->CountData;
				}
				return value;
			}
			return nullptr;
		}

		binary_serializer::BinaryRecord * binary_serializer::ValueFor(const wchar_t * Name, const BinaryType & ExpectedType)
		{
			assert(Name);
			if (Name)
			{
				int64_t ParentId = ID_NOTARCHIVED;
				if (_CurRecord)
				{
					ParentId = _CurRecord->Id;
				}

				// We are trying to find the root record.
				for (size_t i = 0; i < _LastRecord; ++i)
				{
					assert(_Records[i]);
					if (_Records[i])
					{
						if (_Records[i]->Parent == ParentId)
						{
							if (_wcsicmp(Name, _Records[i]->Name) == 0)
							{
								if (_Records[i]->Type == ExpectedType)
								{
									return _Records[i];
								}
							}
						}
					}
				}
			}
			return nullptr;
		}

		uint16_t binary_serializer::CalculateChecksum(file_buffer* buffer)
		{
			// This is actually basically the TCP checksum function.
			// We just assume host order is always little endian.
			uint16_t sum = 0;
			uint16_t buf;
			size_t read;
			for (;;)
			{
				read = buffer->read((char*)&buf, sizeof(buf));
				if (read == 1)
				{
					sum += buf & 0xFF00;
				}
				else if (read == 2)
				{
					sum += buf;
				}
				else
				{
					break;
				}
			} 
			return ~sum;
		}
	}
}